#include <exception>
#include <fstream>
#include <iostream>
#include <sstream>

#include <glm/gtc/type_ptr.hpp>

#include <GL/glew.h>
#include <GLFW/glfw3.h>

#include "glwrapper.hpp"
#include "shader.hpp"

carbon::C_Shader::C_Shader(uint id) : shader_id(id) { }
carbon::C_Shader::~C_Shader() { glDeleteProgram(shader_id); }
void carbon::C_Shader::useShader() { glUseProgram(shader_id); }

carbon::C_Shader* carbon::C_Shader::load(std::string v_shader_file, std::string f_shader_file) {
    if (!carbon::C_GLWrappper::getGlfwStatus()) {
        std::cerr << "error: GLFW are not initialized" << std::endl;
        std::exit(1);
    }
    std::cout << "info: initializing shaders" << std::endl;

    std::string vx_shader_c;
    std::string ft_shader_c;
    std::ifstream vx_file;
    std::ifstream ft_file;

    vx_file.exceptions(std::ifstream::badbit);
    ft_file.exceptions(std::ifstream::badbit);

    try {
        std::cout << "info: parsing shader..." << std::endl;

        vx_file.open(v_shader_file);
        ft_file.open(f_shader_file);
        std::stringstream vx_stream, ft_stream;

        vx_stream << vx_file.rdbuf();
        ft_stream << ft_file.rdbuf();

        vx_file.close();
        ft_file.close();

        vx_shader_c = vx_stream.str();
        ft_shader_c = ft_stream.str();
    }
    catch (std::ifstream::failure &err) {
        std::cerr << "error: failed to load shader" << std::endl;
        std::cerr << "descr: " << err.what() << std::endl;
    }
    std::cout << "info: parsed shader files" << std::endl;

    const GLchar* vx_code = vx_shader_c.c_str();
    const GLchar* ft_code = ft_shader_c.c_str();

    GLuint vertex, fragment;
    GLint success;
    GLchar shader_log[512];

    std::cout << "info: loading vertex shader" << std::endl;

    // TODO: separate shader loaders to make possible loading shaders separately
    //       without compiling them both, i guess it'll be pretty
    //       easy, considering how it works

    // loader for vertex shader
    vertex = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertex, 1, &vx_code, nullptr);
    glCompileShader(vertex);
    glGetShaderiv(vertex, GL_COMPILE_STATUS, &success);

    if (!success) {
        glGetShaderInfoLog(vertex, sizeof(shader_log), nullptr, shader_log);

        std::cerr << "error: can't compile shader: vertex" << std::endl;
        std::cerr << "GL error: " << shader_log << std::endl;

        return nullptr;
    }
    std::cout << "info: loaded vertex shader, loading fragment shader program" << std::endl;

    // loader for fragment shader
    fragment = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragment, 1, &ft_code, nullptr);
    glCompileShader(fragment);
    glGetShaderiv(fragment, GL_COMPILE_STATUS, &success);

    if (!success) {
        glGetShaderInfoLog(fragment, sizeof(shader_log), nullptr, shader_log);

        std::cerr << "error: can't compile shader: fragment" << std::endl;
        std::cerr << "GL error: " << shader_log << std::endl;

        return nullptr;
    }
    std::cout << "info: loaded fragment shader, linking..." << std::endl;

    GLuint sid = glCreateProgram();

    glAttachShader(sid, vertex);
    glAttachShader(sid, fragment);
    glLinkProgram(sid);

    glGetProgramiv(sid, GL_LINK_STATUS, &success);

    if (!success) {
        glGetProgramInfoLog(sid, sizeof(shader_log), nullptr, shader_log);

        std::cerr << "error: can't link shaders" << std::endl;
        std::cerr << "GL error: " << shader_log << std::endl;

        return nullptr;
    }

    glDeleteShader(vertex);
    glDeleteShader(fragment);

    return new carbon::C_Shader(sid); 
}